using Crux.Core.Dapper.SqlGenerator;

namespace Crux.Core.Dapper;
/// <inheritdoc/>
public partial class UnitOfWork<TContext>(
    IDbContextFactory<TContext> dbContextFactory,
    IOptions<DapperConfig<TContext>> options,
    ILogger<UnitOfWork<TContext>> logger)
    : IDisposableObservable, IAsyncDisposableObservable, IUnitOfWork<TContext>
    where TContext : DapperContext
{
    /// <inheritdoc/>
    public TContext Context => _context.Value;
    private IDbConnection connection => Context.Connection;
    /// <inheritdoc/>
    public IDbTransaction? Transaction { get; private set; }
    /// <inheritdoc/>
    public bool IsDisposed { get; private set; }
    internal Lazy<TContext> _context = new Lazy<TContext>(dbContextFactory.CreateDbContext);
    internal void SetContext(Func<TContext> factory)
    {
        _context = new Lazy<TContext>(() => factory());
    }
    private readonly IOptions<DapperConfig> options = options;
    private int Timeout { get; set; } = 30;
    /// <inheritdoc/>
    private SqlGenerator<T> GetSqlGenerator<T>(Action<SqlGenerator<T>>? configure = null) where T : class
    {
        var generator = new SqlGenerator<T>(options.Value);

        configure?.Invoke(generator);

        return generator;
    }
    /// <inheritdoc/>
    public void Dispose()
    {
        if (IsDisposed)
        {
            return;
        }

        IsDisposed = true;

        using var _ = connection;
    }
    /// <inheritdoc/>
    public ValueTask DisposeAsync()
    {
        if (IsDisposed)
        {
            return new ValueTask(Task.CompletedTask);
        }

        IsDisposed = true;

        {
            using var _ = connection;
        }
        {
            using var _ = Transaction;
        }

        return new ValueTask(Task.CompletedTask);
    }
    /// <inheritdoc/>
    public IDbTransaction BeginTransaction()
    {
        return Transaction = Context.BeginTransaction();
    }
    /// <inheritdoc/>
    public IDbTransaction BeginTransaction(IsolationLevel isolationLevel)
    {
        return Transaction = Context.BeginTransaction(isolationLevel);
    }
    /// <inheritdoc/>
    public IUnitOfWork<TContext> SetTimeout(in int timeout)
    {
        Timeout = timeout;

        return this;
    }

    /// <inheritdoc/>
    private async Task<int> ExecuteAsync(string sql, object? parameter, CancellationToken cancellationToken = default)
    {
        var definition = new CommandDefinition(
                    sql,
                    commandTimeout: Timeout,
                    transaction: Transaction,
                    commandType: CommandType.Text,
                    parameters: parameter,
                    cancellationToken: cancellationToken);

        var result = await connection.ExecuteAsync(definition).ConfigureAwait(false);

        return result;
    }
    /// <inheritdoc/>
    private int Execute(string sql, object? parameter)
    {
        var definition = new CommandDefinition(
                    sql,
                    commandTimeout: Timeout,
                    transaction: Transaction,
                    commandType: CommandType.Text,
                    flags: CommandFlags.Buffered,
                    parameters: parameter);

        var result = connection.Execute(definition);

        return result;
    }

    /// <inheritdoc/>
    private long Query(string sql, object? parameter)
    {
        var definition = new CommandDefinition(
                    sql,
                    commandTimeout: Timeout,
                    transaction: Transaction,
                    commandType: CommandType.Text,
                    flags: CommandFlags.Buffered,
                    parameters: parameter);

        var result = connection.Query<long>(definition).FirstOrDefault();

        return result;
    }

    /// <inheritdoc/>
    private async Task<long> QueryAsync(string sql, object? parameter, CancellationToken cancellationToken = default)
    {
        var definition = new CommandDefinition(
                    sql,
                    commandTimeout: Timeout,
                    transaction: Transaction,
                    commandType: CommandType.Text,
                    flags: CommandFlags.Buffered,
                    parameters: parameter,
                    cancellationToken: cancellationToken);

        var result = (await connection.QueryAsync<long>(definition)).FirstOrDefault();

        return result;
    }
}